iT邦幫忙

1

poetry相關指令與應用情境

  • 分享至 

  • xImage
  •  

在剛開始開發時,對於套件管理還有系統路徑的不熟悉
可能安裝不同套件到不同的路徑下,會出現明明有install但是import時找不到module的問題
或是IDE偵測套件的路徑也不同,造成開發上常常會遇到找不到套件的狀況

即使安裝套件到相同的位置,可是不同的專案會需要用到不同的套件,可能不同專案,需要不同版本的同一個套件
pip freeze成requirements.txt時,也沒有辦法分辨哪些套件是這個專案的
在install套件時,除了主要套件,可能其他相依性套件也會被一起安裝
你也不會知道會不會有複數主套件共用同一個相依性套件的情形,如果為了專案整潔而刪除到共用的套件,可能會導致專案無法使用~

這些都是單獨使用pip時可能會遇到的問題
而poetry則是能處理以上痛點的開發工具

接下來就進到poetry的完整介紹,會分成幾點:

  1. poetry簡介
  2. 前置作業
    • 安裝poetry
    • 專案中初始化poetry
  3. poetry相關指令
    • 安裝環境基礎套件
    • 新增套件
    • 更新套件
    • 列出套件
    • 移除套件
    • 其他指令
  4. 使用情境
    • poetry虛擬環境與系統其他軟體的互動(ex: uWSGI)
    • 在docker中使用poetry

Poetry簡介

poetry具有以下特點:

  • 建立虛擬環境確保獨立性與方便管理
  • 套件管理與套件相依性管理
  • 打包與發布套件的功能

為什麼使用poetry?跟virtualenv與pipenv有區別嗎?

  • virtualenv僅提供虛擬環境,沒有其他特殊功用,對套件依賴性也沒有著墨。但是因為非常單純,所以只要在虛擬環境獨立作業,只考慮install不管uninstall以及空間管理時也可以使用,較好上手
  • pipenv除了提供虛擬環境之外,因為具備Pipfile與Pipfile.lock配置檔案,所以執行pipenv install就能快速製作具備指定套件的虛擬環境,並且透過Pipfile來管理套件的依賴性。但是使用上除了bug之外,專案的前景也是很堪憂,可以參考底下連結

而poetry當然也不是完全沒有爭議出現,在這篇中最下方的留言區也有人提到poetry對於deprecated觀念的擔憂,所以可以自己評估這些事情對專案開發時的利弊,決定最終想使用哪一套工具

前置作業

安裝poetry

有三種安裝方式:

  1. curl安裝
  2. pip安裝到獨立的虛擬環境或是專案使用的虛擬環境
  3. pipx安裝

最不建議的就是安裝到專案中的虛擬環境,有幾個原因:

  1. poetry本身就是開發中才需要的套件,開發結束就是單純的佔空間而已,我們需要留著的只有相關的配置檔
  2. poetry依賴套件相當多,可能會造成專案內過於擁擠
  3. poetry的依賴套件可能會跟專案本身的套件發生衝突風險

官方文檔也有提到

Poetry should always be installed in a dedicated virtual environment to isolate it from the rest of your system.

而使用pip的話,需要先自己創建虛擬環境,activate之後再進行安裝,最後再設定PATH

步驟比較繁瑣,而curl安裝也是需要最後設定PATH,相較之下pipx算是最便捷的方式了,因其會自動建立虛擬環境外,也會幫忙把路徑加到PATH中,算是非常方便~

因此我個人建議使用pipx安裝或是curl安裝

使用curl安裝:

  1. 根據作業系統來決定下載安裝包的命令
# linux, macOS, WSL(Windows Subsystem for Linux)
curl -sSL https://install.python-poetry.org | python3 -

# windows
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | py -
  1. 設置環境變數
1. vim ~/.zshrc (or .bashrc)
2. 輸入export PATH=$PATH:$HOME/.local/bin
3. source ~/.zshrc 或是重啟terminal
  1. 確認安裝是否成功,看看該命令有沒有跑出版本
poetry --version

output: Poetry (version 1.7.1)

使用pipx安裝:

  1. 先安裝pipx(如已安裝,可以跳過)
brew install pipx
pipx ensurepath
# pipx ensurepath用於確認執行檔有沒有加到PATH中
  1. 安裝poetry
pipx install poetry
installed package poetry 1.7.1, installed using Python 3.12.1
  These apps are now globally available
    - poetry
⚠️  Note: '/Users/nb050/.local/bin' is not on your PATH environment variable.
    These apps will not be globally accessible until your PATH is updated. Run
    `pipx ensurepath` to automatically add it, or manually modify your PATH in
    your shell's config file (i.e. ~/.bashrc).
done! ✨ 🌟 ✨
  1. 再次執行pipx ensurepath
pipx ensurepath
Success! Added /Users/nb050/.local/bin to the PATH environment variable.

Consider adding shell completions for pipx. Run 'pipx completions' for
instructions.

You will need to open a new terminal or re-login for the PATH changes to take
effect.

Otherwise pipx is ready to go! ✨ 🌟 ✨
  1. 接著重啟terminal就成功了~

專案中初始化poetry

在開始之前,可以先輸入poetry config --list指令去看當下的設定

cache-dir = "/Users/nb050/Library/Caches/pypoetry"
experimental.system-git-client = false
installer.max-workers = null
installer.modern-installation = true
installer.no-binary = null
installer.parallel = true
virtualenvs.create = true
virtualenvs.in-project = null
virtualenvs.options.always-copy = false
virtualenvs.options.no-pip = false
virtualenvs.options.no-setuptools = false
virtualenvs.options.system-site-packages = false
virtualenvs.path = "{cache-dir}/virtualenvs"  # /Users/nb050/Library/Caches/pypoetry/virtualenvs
virtualenvs.prefer-active-python = false
virtualenvs.prompt = "{project_name}-py{python_version}"
warnings.export = true

其中virtualenvs.in-projectv需要改成true,這樣才會在專案內生成虛擬環境

poetry config virtualenvs.in-project true

使用poetry指令之前,必須要有pyproject.toml檔,而要有pyproject.toml檔,我們需要進行初始化的步驟

poetry能快速建立一個python project,並且有包含一些預設檔案

poetry new project_name
.
├── README.md
├── airflow_project
│   └── __init__.py
├── pyproject.toml
└── tests
    └── __init__.py
  • 如果想要內部的包名也更改的話,可以寫
poetry new my-folder --name my-package

此時我們可以看一下pyproject.toml的內容

[tool.poetry]
name = "airflow-project"
version = "0.1.0"
description = ""
authors = ["your_eamil <your_eamil@xxx.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.12"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

tool.poetry中有這個專案的一些基本資訊,如作者、專案名稱等等
而poetry其中一個功能就是能區分開發時依賴(tool.poetry.dev-dependencies)運行時依賴(tool.poetry.dependencies)
以及可以指定build專案的資訊(build-system)

tool.poetry.dependencies 這邊可以注意到poetry會偵測系統的python版本,所以使用pyenv來控制python版本時可以注意一下

那也可以用另一種作法:

先建立專案資料夾以及相關的包,接著在專案資料夾內輸入

poetry init

poetry init幫助建立pyproject.toml檔案,輸入後系統會依序問你想要設置的環境與套件
既然專案內的poetry配置檔都好了,我們就來介紹poetry的相關指令吧~

poetry相關指令

  • 安裝環境基礎套件

既然我們都設定好pyproject.toml了,我們就把我們需要用的套件安裝下來吧~

poetry install
Creating virtualenv airflow-project in /Users/nb050/airflow_project/.venv
Updating dependencies
Resolving dependencies... (0.1s)

Writing lock file

Installing the current project: airflow-project (0.1.0)

可以看到他做了兩件事:

  1. 在這個專案中建立了一個虛擬環境,之後其他需要的套件都會安裝到底下
  2. 如果當前資料夾已經有poetry.lock的話,會直接拿來解析,反之則建立poetry.lock檔案

poetry.lock是根據pyproject.toml的內容生成,但是如果單獨修改pyproject.toml而沒有做後續指令,poetry.lock是不會同步更新的。

內容如下:

# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
package = []

[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "34e39677d8527182346093002688d17a5d2fc204b9eb3e094b2e6ac519028228"

可以看到有儲存python版本之外,最重要的是有儲存content-hash,他是對該套件的內容進行哈希運算,也就是取決於套件的版本、關係與源碼檔案等等,所以今天即使版本相同,但是內容有被竄改,content-hash也會被改變

  • 新增套件

如果我們需要在專案內新增套件,可以使用以下指令

poetry add package_name

如果要指定不同版本,有以下表達方式

# 允許 >=2.0.5, <3.0.0 之間的版本
poetry add package_name@^2.0.5

# 允許 >=2.0.5, <2.1.0 之間的版本
poetry add package_name@~2.0.5

# 允許大於 >=2.0.5 版本,且沒有最新限制
poetry add "package_name>=2.0.5"

# 只允許 2.0.5 版本
poetry add package_name==2.0.5

例如:

poetry add pymysql
Using version ^1.1.0 for pymysql

Updating dependencies
Resolving dependencies... (0.5s)

Package operations: 1 install, 0 updates, 0 removals

  • Installing pymysql (1.1.0)

Writing lock file

可以看到畫面整潔度比起pip install整潔許多XD

  • 更新套件
poetry update
# 或指定特定套件
poetry update package_name

poetry會根據pyproject.toml內的套件或是根據指令的參數來更新特定套件,並且他強大之處在於能在更新套件時一起解析與更新他的依賴套件

  • 列出套件
poetry show
pymysql 1.1.0 Pure Python MySQL Driver

在使用時需要注意:

  1. poetry show主要列出那些直接依賴的套件,如果也要把間接依賴列出來的話,則使用poetry show --all
  2. poetry show是拿poetry.lock的內容來列出來,所以如果toml與lock檔不同步,或是你使用額外非poetry的方式安裝套件,就不會顯示。假如是單純toml與lock檔不同步,可以直接用poetry lock --no-update指令來同步
  3. 可以用poetry show package_name來顯示指定套件的依賴狀況
poetry show apache-airflow
name         : apache-airflow
 version      : 2.8.0
 description  : Programmatically author, schedule and monitor data pipelines

dependencies
 - alembic >=1.6.3,<2.0
 - apache-airflow-providers-common-sql *
 - apache-airflow-providers-ftp *
 - apache-airflow-providers-http *
 - apache-airflow-providers-imap *
 - apache-airflow-providers-sqlite *
 - argcomplete >=1.10
 - asgiref *
  1. 可以用poetry show --tree來看整個樹狀圖
apache-airflow 2.8.0 Programmatically author, schedule and monitor data pipelines
├── alembic >=1.6.3,<2.0
│   ├── importlib-metadata *
│   │   └── zipp >=0.5
│   ├── importlib-resources *
│   │   └── zipp >=3.1.0 (circular dependency aborted here)
│   ├── mako *
│   │   └── markupsafe >=0.9.2
│   ├── sqlalchemy >=1.3.0
│   │   └── greenlet !=0.4.17
│   └── typing-extensions >=4
├── apache-airflow-providers-common-sql *
│   ├── apache-airflow >=2.6.0 (circular dependency aborted here)
│   └── sqlparse >=0.4.2
├── apache-airflow-providers-ftp *
│   └── apache-airflow >=2.6.0 (circular dependency aborted here)
├── apache-airflow-providers-http *
│   ├── aiohttp *
│   │   ├── aiosignal >=1.1.2
│   │   │   └── frozenlist >=1.1.0
│   │   ├── async-timeout >=4.0,<5.0
  • 移除套件
poetry remove package_name
Updating dependencies
Resolving dependencies... (0.1s)

Package operations: 0 installs, 0 updates, 12 removals, 1 skipped

  • Removing certifi (2023.11.17)
  • Removing charset-normalizer (3.3.2)
  • Removing click (8.1.7)
  • Removing idna (3.6)
  • Removing itsdangerous (2.1.2)
  • Removing jinja2 (3.1.2)
  • Removing markupsafe (2.1.3)
  • Removing multidict (6.0.4)
  • Removing packaging (23.2)
  • Removing sniffio (1.3.0)
  • Removing urllib3 (2.1.0)
  • Removing werkzeug (2.3.8)
  • Installing pymysql (1.1.0): Skipped for the following reason: Already installed

在使用時要注意:

  1. 不能remove那些間接依賴型套件,只能刪除直接依賴套件,或是toml檔中設定的
  2. 可以在後面加--dry run來預跑看看
  3. 在刪除直接依賴套件的同時,與pip uninstall不同,會把他底下的間接依賴型套件刪除,同時假如有其他直接依賴套件需要這個間接依賴型套件,那會將他保留,這就是poetry強大的地方
  • 其他指令
poetry export -f requirements.txt --output requirements.txt

需要注意:

  1. --format(-f):輸出的格式,預設為requirements.txt。目前只支援constraints.txt跟requirements.txt
  2. 目前因為有預設安裝Export Poetry Plugin,所以能使用該指令,之後的版本可能需要自己安裝
  3. 在沒有指定參數的狀況下,預設輸出tool.poetry.dependencies內有的套件
  4. 輸出的requirements.txt內含有hash,假如用pip install時可以添加--require-hashes來達到更安全的檢查模式,詳情可以看這篇
poetry shell

會進入到poetry創建的虛擬環境,要退出時用exit而不是deactivate

poetry run

後面接的命就相當於在shell中執行一樣

poetry check

檢查pyproject.tomlpoetry.lock是否一致

poetry lock

會將pyproject.toml中的依賴項鎖定,如果只是想同步lock檔,可以添加—-no-update

poetry env

後面可以接指令,主要是與虛擬環境互動

  • poetry env use /full/path/to/python : 也可以簡寫成python3.x,只要在$PATH有指定的版本,poetry就會用該版本的python來建立虛擬環境
  • poetry env remove /full/path/to/python : 後面的寫法跟上面相同,就是看$PATH。主要作用為刪除特定虛擬環境
  • poetry env info —-path, poetry env list : 列出虛擬環境的路徑以及列出虛擬環境資料夾名稱,但是因為需要當前目錄或父目錄中有pyproject.toml檔,不然找不到

使用情境

  • 一般情境

最佳 Python 套件管理器——Poetry 完全入門指南中有介紹了有關現有專案改用poetry、在別台主機上重現專案的poetry環境、移除並重建虛擬環境等等,內容很清楚,可以直接拜讀

  • poetry虛擬環境與系統其他軟體的互動(ex: uWSGI)

如果專案是用poetry啟動的,那要如何與系統其他軟體做串接呢?

其實很單純,因為虛擬環境中的套件只是獨立存儲在其他的路徑下,因此在調用時注意路徑就好

這邊用nginxuwsgidjango做示範:

  1. 使用poetry來建立新專案
poetry new test_django_uwsgi
  1. 編輯pyproject.toml
[tool.poetry]
name = "test-django-uwsgi"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.9"
django = "^3.2.15"
uwsgi = "^2.0.23"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
  1. 在install要注意有沒有配置poetry config virtualenvs.in-project true 沒有的話,需要先設定,不然就需要下poetry env remove python 來把先前建立的虛擬環境刪除
  2. 初始化poetry install
  3. 用poetry show確認
asgiref           3.7.2        ASGI specs, helper code, and adapters
django            3.2.23       A high-level Python Web framework that encou...
pytz              2023.3.post1 World timezone definitions, modern and histo...
sqlparse          0.4.4        A non-validating SQL parser.
typing-extensions 4.9.0        Backported and Experimental Type Hints for P...
uwsgi             2.0.23       The uWSGI server
  1. 建立django專案
poetry run django-admin startproject my_app

也可以到shell中操作,不能直接打django-admin,不然就不是用虛擬環境的套件了

  1. 修改專案內settings.py中的ALLOWED_HOSTS
ALLOWED_HOSTS = ["your host"]
  1. 接著在專案目錄中建立uwsgi.ini檔
.
├── manage.py
├── my_app
│   ├── asgi.py
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-39.pyc
│   │   ├── settings.cpython-39.pyc
│   │   └── urls.cpython-39.pyc
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── uwsgi.ini
[uwsgi]
socket = 127.0.0.1:7999 #找一個沒被佔用的port
chdir = /path/to/test_django_uwsgi/my_app
wsgi-file=my_app/wsgi.py
processes = 4
threads=2
master = true
vacuum = true
buffer-size=65536
pidfile=uwsgi.pid
uid=123 # 在terminal中輸入id找出當前用戶uid與gid
gid=123
  1. 接著就去/etc/systemd/system,建立service檔
cd /etc/systemd/system
vim my_app.service
[Service]
ExecStart=/path/to/test_django_uwsgi/.venv/bin/uwsgi --ini /path/to/test_django_uwsgi/my_app/uwsgi.ini
Restart=always
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all

[Install]
WantedBy=multi-user.target

這邊的ExecStart前面的uwsgi就是要輸入虛擬環境下的uwsgi,後面就是接ini檔的位置

  1. 啟動服務
# 轉成root
systemctl daemon-reload
systemctl start my_app.service
  1. 觀察uwsgi是否真的有啟動
journalctl -u test_poetry.service -f

有出現Started my_app.service.就成功了~

  1. 雖然已經串起來了,不過為了看到一點畫面,把剩下的nginx設定一下
vim /etc/nginx/nginx.conf
# 確認裡面有沒有底下這行
include /etc/nginx/conf.d/*.conf;
# 或是自己建立其他conf檔路徑
# 在自己建立的conf檔中輸入
server {
    listen 81;
    server_name  test_django;

        location / {
              include uwsgi_params;
              uwsgi_pass 127.0.0.1:7999;
        }
}
nginx -t
# 都沒問題後
systemctl reload nginx
  1. 開啟對應的防火牆
firewall-cmd --zone=public --add-port=81/tcp --permanent
firewall-cmd --reload

最後就能在瀏覽器看到預設的首頁了
https://ithelp.ithome.com.tw/upload/images/20231225/20161866fVqz4f6tOL.png

  • 在docker中使用poetry

在製作docker image時使用poetry來建立環境時可能會遇到幾個問題:

  1. 在製作image時因為需要額外安裝poetry,會增加image的大小
  2. 可能會增加新的耦合,直接使用requirements.txt會更單純

不過假如今天在開發環境都是使用poetry開發的話,poetry匯出的requirements.txt可能使用pip安裝時會有相容性的問題,且現在借助docker的multi-stage builds,可以處理image過大的問題

multi-stage builds也就是在Dockerfile中使用多次的FROM,每一次的FROM都可以用不同的基底image建構,並且可以將這一階段建構的一些內容轉到下一個階段,在最終的階段中篩選掉不需要保留的內容。實際的操作方法可以參考以下的示範流程

以下內容參考自:Blazing fast Python Docker builds with Poetry

我們先用最簡單的方式來建立image檔來看檔案會多大:

當前目錄結構

.
├── Dockerfile
└── pyproject.toml

Dockefile:

FROM python:3.11-buster

RUN apt-get update \
    && apt-get install -y pipx \
    && pipx ensurepath \
    && apt-get install -y python3-venv \
    && pipx install poetry==1.5.0 \
    && pipx ensurepath

COPY pyproject.toml .

ENV PATH="/root/.local/bin:${PATH}"
WORKDIR .
RUN poetry install

pyproject.toml

[tool.poetry]
name = "airflow-project"
version = "0.1.0"
description = ""
authors = ["your_email <your_email@xxx.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = ">=3.8,<3.12"
apache-airflow = { version = "^2.8", python = ">=3.8,<3.12" }
pymysql = "^1.1.0"
apache-airflow-providers-docker = "^3.0.0"
apache-airflow-providers-celery = "^3.0.0"
flower = "^1.0.0"
importlib-metadata = "^4.13.0"
pydantic = "^2.3.0"
pandas = "^1.1.5"
loguru = "^0.5.3"
celery = { version = "*", extras = ["redis"] }

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

這樣做出來的大小有1.72GB這麼大,因此我們需要將它慢慢瘦身

但在此之前,先提一些做這個基本image的小細節

  1. 指定好poetry要安裝時的版本,避免新版本發佈時可能導致服務出現問題。但是之前有出現過一些爭議,poetry有透過一些方式來讓使用者被迫升級新版本,所以這部分還是得自己注意
  2. 如果pyproject.toml中包含tool.poetry.dev-dependencies 時,可以在poetry install的指令改成poetry install --without dev

設置相關參數

依照個人需求來設置環境變數

  • POETRY_VIRTUALENVS_IN_PROJECT=true:設置為true時,poetry會在專案的根目錄底下的.venv中建立虛擬環境
  • POETRY_VIRTUALENVS_CREATE=true:如果還沒建立虛擬環境時會建立一個虛擬環境
  • POETRY_CACHE_DIR:設置cache的資料夾位置

額外建立虛擬環境都是避免影響到系統的python或是poetry,讓這三者能彼此獨立

清除poetry cache

因為poetry會保存安裝套件的快取,但是我們基本上不會再需要這些檔案,所以可以將他們清除

此時的Dockerfile如下

FROM python:3.11-buster

RUN apt-get update \
    && apt-get install -y pipx \
    && pipx ensurepath \
    && apt-get install -y python3-venv \
    && pipx install poetry==1.5.0 \
    && pipx ensurepath

COPY pyproject.toml .

ENV PATH="/root/.local/bin:${PATH}" \
    POETRY_VIRTUALENVS_IN_PROJECT=true \
    POETRY_VIRTUALENVS_CREATE=1 \
    POETRY_CACHE_DIR=/tmp/poetry_cache

WORKDIR .
RUN poetry install \
    && rm -rf $POETRY_CACHE_DIR

使用multi-stage builds

修改Dockfile

FROM python:3.11-buster as builder

RUN apt-get update \
    && apt-get install -y pipx \
    && pipx ensurepath \
    && apt-get install -y python3-venv \
    && pipx install poetry==1.5.0 \
    && pipx ensurepath

COPY pyproject.toml .

ENV PATH="/root/.local/bin:${PATH}" \
    POETRY_VIRTUALENVS_IN_PROJECT=true \
    POETRY_VIRTUALENVS_CREATE=1 \
    POETRY_CACHE_DIR=/tmp/poetry_cache

WORKDIR .

RUN poetry install \
    && rm -rf $POETRY_CACHE_DIR
# the runtime image, used to just run the code provided its virtual environment
FROM python:3.11-slim-buster as runtime

ENV VIRTUAL_ENV=./.venv \
    PATH="./.venv/bin:$PATH"

COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}

以上可以注意幾點:

  1. 在容器運行的階段,我們不會再需要poetry留在容器中,並且poetry也只是幫助我們建立一開始的虛擬環境與安裝套件在路徑下。所以安裝完後,只需要將路徑轉移到下一個階段就好
  2. slim僅包含運行python所需的最小包,而我們額外需要運行的套件均在虛擬環境下了,所以使用較小的base image能更好控制image的大小

最後我們的成品大小為432MB,為原本的1/4左右,算是蠻成功的一次瘦身~

當然在這邊如果需要再去增進image製作的速度等更細節的優化,可以去拜讀原本的文章,這邊就不再特別說明了~

以上就是有關poetry的一些使用場景,我沒有使用太多其他套件管理相關工具的經驗,所以這篇與其說是要推坑poetry,反而比較像是要給那些已經想使用,但是可能還沒有相關知識的人去看。並且這對我來說,也是方便我自己在使用時,不用一直去翻相關的文檔,直接透過這一篇來進行部署或是操作等等,希望能幫助到想使用的人~

參考文章:


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言